package com.daily.taskcenter.service.impl;

import com.daily.taskcenter.config.DyTaskInfo;
import com.daily.taskcenter.entity.DynamicTaskConfig;
import com.daily.taskcenter.exception.ServiceException;
import com.daily.taskcenter.service.DyTaskManager;
import com.daily.taskcenter.service.IDynamicTaskConfigService;
import com.daily.taskcenter.tasks.DyTask;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.CronTask;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;

/**
 * @author z
 * @date 2019/7/25 11:53
 **/
@Service
@Slf4j
public class DefaultDyTaskManager implements DyTaskManager, CommandLineRunner, ApplicationContextAware {

    private ApplicationContext context;

    Map<String, DyTaskInfo> taskMap = new ConcurrentHashMap<>();

    @Autowired
    private ThreadPoolTaskScheduler taskScheduler;

    @Autowired
    private IDynamicTaskConfigService dynamicTaskConfigService;

    /**
     * 取消定时任务，根据传入的beanName
     *
     * @param beanName
     * @return
     */
    @Override
    public boolean cancelTask(String beanName) {
        if (StringUtils.isBlank(beanName)) {
            return false;
        }
        // 若之前已启动过该任务，则先将该任务取消
        DyTaskInfo task = taskMap.remove(beanName);
        if (task != null && task.getFuture() != null) {
            log.info("取消任务执行, beanName = {}", beanName);
            task.getFuture().cancel(true);
            return true;
        }
        return false;
    }

    @Override
    public boolean cancelTask(Long taskId) {

        // 通过taskId获取到task配置信息
        DynamicTaskConfig taskConfig = dynamicTaskConfigService.getById(taskId);

        if (null == taskConfig) {
            throw new ServiceException("任务ID " + taskId + " 不存在");
        }

        return cancelTask(taskConfig.getBeanName());
    }

    /**
     * 直接执行定时任务，相当于手动触发调用
     *
     * @param beanName
     * @return
     */
    @Override
    public boolean runTask(String beanName) {
        DyTask target = null;
        try {
            target = (DyTask) context.getBean(beanName);
        } catch (Throwable e) {
            throw new ServiceException("无法获取bean信息, 无法执行任务:" + e.getMessage());
        }

        // 执行
        log.info("直接执行定时任务，相当于手动触发调用, beanName = {}", beanName);
        target.run();

        return true;
    }

    @Override
    public boolean runTask(Long taskId) {

        // 通过taskId获取到task配置信息
        DynamicTaskConfig taskConfig = dynamicTaskConfigService.getById(taskId);

        if (null == taskConfig) {
            throw new ServiceException("任务ID " + taskId + " 不存在");
        }

        return runTask(taskConfig.getBeanName());
    }

    /**
     * 关键方法，开始定时任务
     *
     * @param taskId
     * @return
     */
    public boolean scheduleTask(Long taskId) {

        // 通过taskId获取到task配置信息
        DynamicTaskConfig taskConfig = dynamicTaskConfigService.getById(taskId);

        if (null == taskConfig) {
            throw new ServiceException("任务ID " + taskId + " 不存在");
        }

        return scheduleTask(taskConfig.getBeanName(), taskConfig.getCron());
    }

    /**
     * 关键方法，开始定时任务
     *
     * @param beanName
     * @param cron
     * @return
     */
    @Override
    public boolean scheduleTask(String beanName, String cron) {
        DyTask target = null;
        try {
            // 从 spring 容器中获取到执行器的bean
            target = (DyTask) context.getBean(beanName);
        } catch (Throwable e) {
            throw new ServiceException("无法获取bean信息, 无法执行任务:" + e.getMessage());
        }

        Method method = null;
        try {
            // 通过该bean，反射获取到该bean的run方法
            method = target.getClass().getMethod("run");
        } catch (NoSuchMethodException e) {
            throw new ServiceException("无法获取run方法, 无法执行任务:" + e.getMessage());
        }

        try {
            // 构建CronTask，将目标执行器 + cron表达式传入，设置时区为：系统默认时区
            CronTask cronTask = new CronTask(createRunnable(target, method),
                    new CronTrigger(cron, TimeZone.getTimeZone(ZoneId.systemDefault())));

            // 构建任务执行器
            DyTaskInfo taskInfo = new DyTaskInfo(cronTask);

            // 启动执行器
            ScheduledFuture<?> future = taskScheduler.schedule(cronTask.getRunnable(), cronTask.getTrigger());
            // 将执行结果异步放入执行任务对象中
            taskInfo.setFuture(future);

            // 调用取消任务
            //log.info("若该任务已存在，则先将已存在的任务先取消");
            cancelTask(beanName);

            // 以beanName为key，任务对象为value，将其放入taskMap中，核心业务逻辑完成
            taskMap.put(beanName, taskInfo);

            return true;
        } catch (Throwable e) {
            log.info("配置异常:{}", e.getMessage());
        }

        return true;
    }

    /**
     * 关键方法
     *
     * @param target
     * @param method
     * @return
     */
    private Runnable createRunnable(Object target, Method method) {
        // 通过AopUtils获取到调用的方法对象，
        Method invocableMethod = AopUtils.selectInvocableMethod(method, target.getClass());
        // 并将其包装成ScheduledMethodRunnable返回，用于创建定时任务
        // ScheduledMethodRunnable 实现了 Runnable run方法
        // 其线程run方法内部逻辑实现了通过传入的目标实例 + 目标方法进行执行
        return new ScheduledMethodRunnable(target, invocableMethod);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    /**
     * 此方法将在服务启动完毕后自动调用
     *
     * @param args
     */
    @Override
    public void run(String... args) {
        //log.info("此方法将在服务启动完毕后自动调用");
        // 查询到配置表中所有任务
        List<DynamicTaskConfig> taskConfigs = dynamicTaskConfigService.findAllByStatus(0);
        // 遍历，初始化任务到
        taskConfigs.forEach(config -> {
            if (StringUtils.isNotBlank(config.getBeanName()) && StringUtils.isNotBlank(config.getCron())) {
                try {
                    log.info(">>> 开启定时任务: {},{},{}", config.getName(), config.getBeanName(), config.getCron());
                    scheduleTask(config.getBeanName(), config.getCron());
                } catch (Throwable e) {
                    log.error("初始化任务失败: {}", e.getMessage());
                }
            }
        });
    }
}
